﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Collections;
using System.Text;
using System.Reflection;
using System.IO;
using System.Xml.Serialization;

namespace ESLReminder.Modules
{
    /// <summary>
    /// This class manages all plugins.</summary>
    /// <remarks>
    /// Plugins should only be loaded through this interface.</remarks>
    public class PluginCollection<T> : PluginCollectionBase where T : IPlugin
    {
        /*public IEnumerator GetEnumerator()
        {
            return m_PluginInstances.GetEnumerator();
        }*/
        /*public void Add(object plugin)
        {
            AddInstanciatedPlugin(plugin);
        }*/
        protected List<T> m_PluginInstances = new List<T>();
        protected List<Type> m_KnownPluginTypes = new List<Type>();
        protected IPluginHost m_Host = null;

        #region Constructor and Finalizer

        /// <summary>
        /// Creates a new PluginCollection.</summary>
        /// <param name="host">
        /// The host that is instanciating this new collection.</param>
        public PluginCollection(IPluginHost host)
        {
            m_Host = host;
        }

        public PluginCollection()
        {
        }

        /// <summary>
        /// Finalizer</summary>
        ~PluginCollection()
        {
            //foreach (T p in m_PluginInstances)
                //SaveSettings(p);
        }

        #endregion

        #region Retrieve Plugin Instances

        /// <summary>
        /// Gets the plugins instanciated by this specific collection.</summary>
        /// <returns>
        /// Array of plugin instances loaded through this collection.</returns>
        /// <seealso cref="GetGlobalPluginInstances()">
        /// Use GetGlobalPluginInstances() to get all instances of a specific type.</seealso>
        public T[] PluginInstances
        {
            get
            {
                return m_PluginInstances.ToArray();
            }
            set
            {
                foreach (T p in m_PluginInstances)
                    m_GlobalPluginInstances.Remove(p);
                m_PluginInstances.Clear();
                foreach (T p in value)
                {
                    //m_PluginInstances.Add(p);
                    //m_GlobalPluginInstances.Add(p);
                    LoadPlugin(p.GetType());
                }
            }
        }

        /// <summary>
        /// Gets the plugins instanciated by this specific collection.</summary>
        /// <returns>
        /// Array of plugin types loaded through this collection.</returns>
        [XmlIgnore]
        public Type[] PluginTypes
        {
            get
            {
                return m_KnownPluginTypes.ToArray();
            }
        }

        private void AddInstanciatedPlugin(T p)
        {
            m_PluginInstances.Add(p);
            m_GlobalPluginInstances.Add(p);

            p.PluginInit(m_Host);

            foreach (IPlugin existingPlugin in m_GlobalPluginInstances)
            {
                try
                {
                    p.OtherPluginLoaded(existingPlugin);
                    existingPlugin.OtherPluginLoaded(p);
                }
                catch (Exception ex)
                {
                    System.Diagnostics.Trace.WriteLine("Error in OtherPluginLoaded:");
                    System.Diagnostics.Trace.WriteLine(ex.ToString());
                }
            }

            // TODO: the plugin should be known when this is called, so should we really try to add the type?
            //if (!m_KnownPluginTypes.Contains(p.GetType()))
                //m_KnownPluginTypes.Add(p.GetType());
            
        }

        public T AddNewPluginInstance(Type t)
        {
            try
            {
                T p = (T)Activator.CreateInstance(t);
                // p is either a valid plugin at this point or we have thrown an exception above
                AddInstanciatedPlugin(p);
                return p;
            }
            catch (Exception ex)
            {
                // Constructor failed
                System.Diagnostics.Trace.WriteLine(String.Format("Plugin Interface: could not create plugin {0}: {1}", t.ToString(), ex.Message));
                if (ex.InnerException != null)
                    System.Diagnostics.Trace.WriteLine("More info: " + ex.InnerException.Message);
            }
            return default(T);
        }

        public T GetPluginForType(Type t)
        {
            foreach (T p in m_PluginInstances)
            {
                if (p.GetType() == t)
                    return p;
            }
            return default(T);
        }

        /// <summary>
        /// Gets all instances of a specific type throughout all plugin collections.</summary>
        /// <returns>
        /// Array of plugin instances loaded through any collection.</returns>
        /// <seealso cref="GetPluginInstances()">
        /// Use GetPluginInstances() to get an array of plugins loaded from a specific collection.</seealso>
        public static T[] GetGlobalPluginInstances()
        {
            List<T> results = new List<T>();
            foreach (IPlugin p in m_GlobalPluginInstances)
            {
                // check for plugin interface
                if (typeof(T).IsAssignableFrom(p.GetType()))
                {
                    results.Add((T)p);
                }
            }
            return results.ToArray();
        }

        #endregion

        #region Loading Plugins from Files and Assemblies

        /// <summary>
        /// Loads every plugin found in the Assembly that fits this collection's Type.
        /// This creates an instance of every plugin type found and adds it to both
        /// the collection specific plugin list and the global plugin list</summary>
        /// <param name="asm">
        /// The assembly from which to load all suitable plugins</param>
        /// <seealso cref="LoadPlugin(Type)">
        /// LoadPlugin is called by this function for each plugin type found</seealso>
        public void LoadPlugins(Assembly asm)
        {
            Type[] types = asm.GetTypes();
            LoadPlugins(types);
        }

        public void LoadPlugins(Type[] types)
        {
            foreach (Type t in types)
            {
                try
                {
                    // check for plugin interface
                    if (typeof(T).IsAssignableFrom(t) && !t.IsAbstract)
                    {
                        LoadPlugin(t);
                    }
                }
                catch (Exception ex)
                {
                    System.Diagnostics.Trace.WriteLine(String.Format("Plugin Interface: could not load plugin {0}: {1}", t.ToString(), ex.Message));
                    if (ex.InnerException != null)
                        System.Diagnostics.Trace.WriteLine("More info: " + ex.InnerException.Message);
                }
            }
        }

        /// <summary>
        /// Instanciates a new plugin of the given type and adds it to the plugin lists</summary>
        /// <param name="t">
        /// The type of the plugin to create an instance for</param>
        /// <seealso cref="LoadPlugins(Assembly)">
        /// LoadPlugin is called by this function for each plugin type found</seealso>
        public void LoadPlugin(Type t)
        {
            /*T p;
            XmlSerializer xml = new XmlSerializer(t);
            try
            {
                using (StreamReader s = new StreamReader(Path.Combine(Path.Combine(GlobalSettings.RuntimeSettings.SettingsDir, "Plugins"), t.Name + ".qrm")))
                {
                    p = (T)xml.Deserialize(s);
                }
            }
            catch (Exception)
            {
                // deserializing failed, create new instance
                p = (T)Activator.CreateInstance(t);
            }
            // p is either a valid plugin at this point or we have thrown an exception above
            m_PluginInstances.Add(p);
            m_GlobalPluginInstances.Add(p);*/
            if (!m_KnownPluginTypes.Contains(t))
                m_KnownPluginTypes.Add(t);
            /*if (p.Enabled)
            {
                p.PluginInit(m_Host);
                foreach (T plugin in m_GlobalPluginInstances)
                {
                    if (plugin.Equals(p))
                        continue;
                    plugin.OtherPluginLoaded(p);
                    p.OtherPluginLoaded(plugin);
                }
            }*/
            System.Diagnostics.Trace.WriteLine(String.Format("Plugin Interface: Successfully loaded type {0} from plugin {1}", t.Name, Path.GetFileName(t.Assembly.Location)));
        }

        /// <summary>
        /// Scans a given path for dll files containing Assemblies with plugins to load</summary>
        /// <param name="path">
        /// The path to scan (recurses into subdirectories)</param>
        /// <seealso cref="LoadPlugins(Assembly)">
        /// LoadPlugins is called by this function for each Assembly</seealso>
        public void LoadPlugins(string path)
        {
            if (!Directory.Exists(path))
                return;
            string[] pluginFiles = Directory.GetFiles(path, "*.dll", SearchOption.AllDirectories);

            foreach (string file in pluginFiles)
            {
                Assembly asm = Assembly.LoadFile(file);
                try
                {
                    if (asm != null)
                    {
                        LoadPlugins(asm);
                    }
                }
                catch (Exception ex)
                {
                    System.Diagnostics.Trace.WriteLine(String.Format("Error loading plugin file '{0}': {1}", file, ex.Message));
                }
            }
        }

        #endregion

        #region Saving and Restoring Plugin Instances

        /*public void SaveSettings(IPlugin p)
        {
            XmlSerializer xml = new XmlSerializer(p.GetType());
            DirectoryInfo d = new DirectoryInfo(Path.Combine(GlobalSettings.RuntimeSettings.SettingsDir, "Plugins"));

            try
            {
                if (!d.Exists)
                    d.Create();
                using (StreamWriter s = new StreamWriter(Path.Combine(d.FullName, p.GetType().Name + ".qrm")))
                {
                    xml.Serialize(s, p);
                }
            }
            catch (Exception ex)
            {
                System.Diagnostics.Trace.WriteLine("Plugin error: " + ex.Message);
            }
        }*/

        public bool LoadInstanciatedPlugins(string file)
        {
            try
            {
                Type[] types = m_KnownPluginTypes.ToArray();

                XmlSerializer deSerializer = new XmlSerializer(m_PluginInstances.GetType(), types);
                using (StreamReader s = new StreamReader(file))
                {
                    List<T> collection = (List<T>)deSerializer.Deserialize(s);
                    s.Close();
                    foreach (T p in collection)
                    {
                        AddInstanciatedPlugin(p);
                    }
                }
            }
            catch (Exception ex)
            {
                System.Diagnostics.Trace.WriteLine(String.Format("Could not load plugins from file '{0}: {1}'", file, ex.Message));
                if (ex.InnerException != null)
                    System.Diagnostics.Trace.WriteLine(String.Format("Details: {0}'", ex.InnerException.Message));
                return false;
            }
            return true;
        }

        public void SaveInstanciatedPlugins(string file)
        {
            try
            {
                Type[] types = m_KnownPluginTypes.ToArray();

                XmlSerializer serializer = new XmlSerializer(m_PluginInstances.GetType(), types);
                using (StreamWriter s = new StreamWriter(file))
                {
                    serializer.Serialize(s, m_PluginInstances);
                }
            }
            catch (Exception ex)
            {
                System.Diagnostics.Trace.WriteLine(String.Format("Could not save plugins to file '{0}: {1}'", file, ex.Message));
                if (ex.InnerException != null)
                    System.Diagnostics.Trace.WriteLine(String.Format("Details: {0}'", ex.InnerException.Message));
            }
        }

        #endregion
    }
    public class PluginCollectionBase
    {
        protected static List<IPlugin> m_GlobalPluginInstances = new List<IPlugin>();
    }
}
